/*
 * SqEngine 0.5.1
 * Date 27/08/2009 
 * by squid <squid@stormwind.it>
 * web: http://squareengine.codeplex.com
 * Object Entity Component
 */

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using JigLibX.Collision;
using JigLibX.Physics;
using JigLibX.Geometry;
using JigLibX.Math;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Utils;
using SqEngine.Components;
using sqengine.Components;
using sqengine.Engine;
using sqengine.Engine.Common;
#endregion


namespace sqengine.PhysicObjects
{
    public abstract class BasicObject : Entity
    {
        #region Variables

        private Camera _camera;
        private Model model;
        public Vector3 modelVelocity;
        private Texture2D _texture;

        
        private bool drawcustomeffect = false;
        public bool IsCustomEffect { get { return drawcustomeffect; } set { drawcustomeffect = value; } }

        protected Body body;
        protected CollisionSkin collision;
        private VertexPositionColor[] wf;
        protected Matrix[] boneTransforms = null;
        protected int boneCount = 0;

        public Camera CurrentCamera { get { return _camera; } }
        public Texture2D ObjectTexture { get { return _texture; } set { _texture = value; } }
        #endregion

        #region Properties

        public Model Model { get { return model; } set { model = value; } }

        public Body PhysicsBody { get { return body; } }
        public CollisionSkin PhysicsSkin { get { return collision; } }


        public void SetTexture(string filename) { _texture = this.Adcontent.LoadTexture2D(filename); }
        public void SetColor(Color ColorObj) { ObjColor = ColorObj.ToVector3(); }

        #endregion

        #region Terrain Effect
        private Texture2D day_texture, night_texture, sunset_texture;
        private Effect _sky_effect;
        private SkyDome _skydome;
        #endregion

        public BasicObject(Game game, Model model, string texture)
            : base(game)
        {
            //Load default camera and content loader
            UpdateCamera();

            this.model = model;

            if (!string.IsNullOrEmpty(texture))
                SetTexture(texture);

            Position = Vector3.Zero;
            RotationX(0);
            RotationY(0);
            RotationZ(0);
            Scale = 1.0f * Vector3.One;
            LocalWorld = Matrix.Identity;

        }

        public override void Initialize()
        {
            base.Initialize();
        }
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            //Update camera
            UpdateCamera();

            if (!drawcustomeffect)
                DrawDefaultBasicEffect(gameTime);
            else
                DrawDefaultBasicEffect(gameTime);


            //Show collision box
            DrawDebug((((sqengine)this.Game).DebugDrawer.Enabled));

            base.Draw(gameTime);
        }
        public abstract void ApplyEffects(BasicEffect effect);

        protected Vector3 SetMass(float mass)
        {
            PrimitiveProperties primitiveProperties =
                new PrimitiveProperties(PrimitiveProperties.MassDistributionEnum.Solid, PrimitiveProperties.MassTypeEnum.Density, mass);

            float junk; Vector3 com; Matrix it, itCoM;

            collision.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);
            body.BodyInertia = itCoM;
            body.Mass = junk;

            return com;
        }

        private void DrawDebug(bool state)
        {
            if (collision != null)
            {
                wf = collision.GetLocalSkinWireframe();

                // if the collision skin was also added to the body
                // we have to transform the skin wireframe to the body space
                if (body.CollisionSkin != null)
                {
                    body.TransformWireframe(wf);
                }

                ((sqengine)this.Game).DebugDrawer.DrawShape(wf);
            }

        }

        private static void RemapModel(Model model, Effect effect)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = effect;
                }
            }
        }

        private void UpdateCamera()
        {
            _camera = ((sqengine)this.Game).Camera;

        }

        private void DrawDefaultBasicEffect(GameTime gameTime)
        {
            if (model != null)
            {
                if (boneTransforms == null || boneCount != model.Bones.Count)
                {
                    boneTransforms = new Matrix[model.Bones.Count];
                    boneCount = model.Bones.Count;
                }

                model.CopyAbsoluteBoneTransformsTo(boneTransforms);
                float depthBias = GraphicsDevice.RenderState.DepthBias;


                UpdateCamera();

                foreach (ModelMesh mesh in model.Meshes)
                {

                    foreach (BasicEffect effect in mesh.Effects)
                    {
                        // the body has an orientation but also the primitives in the collision skin
                        // owned by the body can be rotated!
                        if (body != null)
                        {
                            if (body.CollisionSkin != null)
                            {
                                LocalWorld = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(Scale)
                                    //* Matrix.CreateRotationY(Rotation.Y)
                                    //* Matrix.CreateRotationX(Rotation.X)
                                    //* Matrix.CreateRotationZ(Rotation.Z)
                                             * body.CollisionSkin.GetPrimitiveLocal(0).Transform.Orientation
                                             * body.Orientation
                                             * Matrix.CreateTranslation(body.Position);


                            }
                            else
                            {

                                LocalWorld = boneTransforms[mesh.ParentBone.Index] * Matrix.CreateScale(Scale)
                                             * Matrix.CreateRotationY(Rotation.Y)
                                             * Matrix.CreateRotationX(Rotation.X)
                                             * Matrix.CreateRotationZ(Rotation.Z)
                                             * body.Orientation
                                             * Matrix.CreateTranslation(body.Position);

                            }
                        }                       
                        effect.World = LocalWorld;
                        effect.View = _camera.View;
                        effect.Projection = _camera.Projection;


                        ApplyEffects(effect);
                       // effect.Alpha = 1.0f;

                        if (_texture != null)
                        {
                            effect.TextureEnabled = true;
                            effect.Texture = _texture;
                        }
                        effect.EnableDefaultLighting();
                        effect.PreferPerPixelLighting = true;


                    }

                    mesh.Draw();

                }


            }
        }

        private void DrawTerrainEffect(GameTime gameTime)
        {
            Matrix View = _camera.View;
            Matrix Projection = _camera.Projection;

            RemapModel(model, _sky_effect);

            foreach (ModelMesh mesh in model.Meshes)
            {
                //Matrix World = body.Orientation;

                LocalWorld = Matrix.Identity;

                if (body.CollisionSkin != null)
                {
                    LocalWorld = boneTransforms[mesh.ParentBone.Index] 
                    * Matrix.CreateRotationY(Rotation.Y)
                    * Matrix.CreateRotationX(Rotation.X)
                    * Matrix.CreateRotationZ(Rotation.Z)
                    * body.CollisionSkin.GetPrimitiveLocal(0).Transform.Orientation
                    * body.Orientation
                    * Matrix.CreateScale(Scale)
                    * Matrix.CreateTranslation(body.Position);


                }
                else
                {
                    LocalWorld = boneTransforms[mesh.ParentBone.Index] 
                                           * Matrix.CreateRotationY(Rotation.Y)
                                           * Matrix.CreateRotationX(Rotation.X)
                                           * Matrix.CreateRotationZ(Rotation.Z)
                                           * body.Orientation
                                           * Matrix.CreateScale(Scale)
                                           * Matrix.CreateTranslation(body.Position);
                                           
                }

                Matrix World = body.Orientation;
                Matrix WorldIT = Matrix.Invert(LocalWorld);
                WorldIT = Matrix.Transpose(WorldIT);

                if (mesh.Effects.Count > 0)
                {
                    foreach (Effect effect in mesh.Effects)
                    {


                        effect.Parameters["WorldIT"].SetValue(WorldIT);
                        effect.Parameters["WorldViewProj"].SetValue(LocalWorld * _camera.View * _camera.Projection);
                        effect.Parameters["ViewInv"].SetValue(Matrix.Invert(View));
                        effect.Parameters["World"].SetValue(LocalWorld);

                        if (_texture != null)
                            effect.Parameters["DiffuseTexture"].SetValue(_texture);

                        effect.Parameters["SkyTextureNight"].SetValue(night_texture);
                        effect.Parameters["SkyTextureSunset"].SetValue(sunset_texture);
                        effect.Parameters["SkyTextureDay"].SetValue(day_texture);

                        effect.Parameters["isSkydome"].SetValue(false);

                        effect.Parameters["LightDirection"].SetValue(-_skydome.Parameters.LightDirection);
                        effect.Parameters["LightColor"].SetValue(_skydome.Parameters.LightColor);
                        effect.Parameters["LightColorAmbient"].SetValue(_skydome.Parameters.LightColorAmbient);
                        effect.Parameters["FogColor"].SetValue(_skydome.Parameters.FogColor);
                        effect.Parameters["fDensity"].SetValue(0.0003f);
                        effect.Parameters["SunLightness"].SetValue(_skydome.Parameters.SunLightness);
                        effect.Parameters["sunRadiusAttenuation"].SetValue(_skydome.Parameters.SunRadiusAttenuation);
                        effect.Parameters["largeSunLightness"].SetValue(_skydome.Parameters.LargeSunLightness);
                        effect.Parameters["largeSunRadiusAttenuation"].SetValue(_skydome.Parameters.LargeSunRadiusAttenuation);
                        effect.Parameters["dayToSunsetSharpness"].SetValue(_skydome.Parameters.DayToSunsetSharpness);
                        effect.Parameters["hazeTopAltitude"].SetValue(_skydome.Parameters.HazeTopAltitude);

                    }
                    mesh.Draw();
                }
            }
        }


        public void SetSkydome(SkyDome skydome, Texture2D day, Texture2D sunset, Texture2D night, Effect sky_effect)
        {
            _skydome = skydome;
            day_texture = day;
            sunset_texture = sunset;
            night_texture = night;
            _sky_effect = sky_effect;
            _sky_effect.CurrentTechnique = _sky_effect.Techniques["SkyDome"];

            drawcustomeffect = true;

            RemapModel(model, _sky_effect);
        }

        protected Box BuildPhysicsBoxFromModel(Model model, Matrix matrix)
        {
            BoundingBox boundingBox = BuildBoundingBoxFromModel(model, matrix);
            Vector3 sides = new Vector3(boundingBox.Max.X - boundingBox.Min.X,
                boundingBox.Max.Y - boundingBox.Min.Y,
                boundingBox.Max.Z - boundingBox.Min.Z);
            return new Box(boundingBox.Min, matrix, sides);
        }

        protected Box BuildPhysicsBoxFromModel(Model model, Matrix matrix, Vector3 scale)
        {
            BoundingBox boundingBox = GetBoundingBoxScaled(BuildBoundingBoxFromModel(model, matrix), scale);
            
            Vector3 sides = new Vector3(boundingBox.Max.X - boundingBox.Min.X,
               boundingBox.Max.Y - boundingBox.Min.Y,
               boundingBox.Max.Z - boundingBox.Min.Z);
            return new Box(boundingBox.Min, matrix, sides);

        }

        protected BoundingBox BuildBoundingBoxFromModel(Model model, Matrix world)
        {
            Vector3 Max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
            Vector3 Min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);

            Matrix[] bones = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(bones);

            foreach (ModelMesh mesh in model.Meshes)
            {
                Matrix transform = bones[mesh.ParentBone.Index] * world;

                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    int stride = part.VertexStride;
                    int numberv = part.NumVertices;
                    byte[] vertexData = new byte[stride * numberv];

                    mesh.VertexBuffer.GetData(vertexData);

                    for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
                    {
                        float floatvaluex = BitConverter.ToSingle(vertexData, ndx);
                        float floatvaluey = BitConverter.ToSingle(vertexData, ndx + 4);
                        float floatvaluez = BitConverter.ToSingle(vertexData, ndx + 8);
                        Vector3 vectCurrentVertex = new Vector3(floatvaluex, floatvaluey, floatvaluez);
                        Vector3 vectWorldVertex = Vector3.Transform(vectCurrentVertex, transform);

                        if (vectWorldVertex.X < Min.X) Min.X = vectWorldVertex.X;
                        if (vectWorldVertex.X > Max.X) Max.X = vectWorldVertex.X;
                        if (vectWorldVertex.Y < Min.Y) Min.Y = vectWorldVertex.Y;
                        if (vectWorldVertex.Y > Max.Y) Max.Y = vectWorldVertex.Y;
                        if (vectWorldVertex.Z < Min.Z) Min.Z = vectWorldVertex.Z;
                        if (vectWorldVertex.Z > Max.Z) Max.Z = vectWorldVertex.Z;
                    }
                }
            }

            return new BoundingBox(Min, Max);
        }

        protected BoundingBox GetBoundingBoxScaled(BoundingBox bndbox, Vector3 scale)
        {
            BoundingBox tmpbnd = bndbox;
            tmpbnd.Min = Vector3.Multiply(tmpbnd.Min, scale);
            tmpbnd.Max = Vector3.Multiply(bndbox.Max, scale);

            return tmpbnd;

        }


        /// <summary>
        /// Helper Method to get the vertex and index List from the model.
        /// </summary>
        /// <param name="vertices"></param>
        /// <param name="indices"></param>
        /// <param name="model"></param>
        protected void ExtractData(List<Vector3> vertices, List<TriangleVertexIndices> indices, Model model)
        {
            Matrix[] bones_ = new Matrix[model.Bones.Count];
            model.CopyAbsoluteBoneTransformsTo(bones_);
            foreach (ModelMesh mm in model.Meshes)
            {
                Matrix xform = bones_[mm.ParentBone.Index];
                foreach (ModelMeshPart mmp in mm.MeshParts)
                {
                    int offset = vertices.Count;
                    Vector3[] a = new Vector3[mmp.NumVertices];
                    mm.VertexBuffer.GetData<Vector3>(mmp.StreamOffset + mmp.BaseVertex * mmp.VertexStride,
                        a, 0, mmp.NumVertices, mmp.VertexStride);
                    for (int i = 0; i != a.Length; ++i)
                        Vector3.Transform(ref a[i], ref xform, out a[i]);
                    vertices.AddRange(a);

                    if (mm.IndexBuffer.IndexElementSize != IndexElementSize.SixteenBits)
                        throw new Exception(
                            String.Format("Model uses 32-bit indices, which are not supported."));
                    short[] s = new short[mmp.PrimitiveCount * 3];
                    mm.IndexBuffer.GetData<short>(mmp.StartIndex * 2, s, 0, mmp.PrimitiveCount * 3);
                    JigLibX.Geometry.TriangleVertexIndices[] tvi = new JigLibX.Geometry.TriangleVertexIndices[mmp.PrimitiveCount];
                    for (int i = 0; i != tvi.Length; ++i)
                    {
                        tvi[i].I0 = s[i * 3 + 2] + offset;
                        tvi[i].I1 = s[i * 3 + 1] + offset;
                        tvi[i].I2 = s[i * 3 + 0] + offset;
                    }
                    indices.AddRange(tvi);
                }
            }
        }


    }
}